package info.sean.util.common.money;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Currency;

public class Money implements Serializable, Comparable<Object> {
    private static final long serialVersionUID = 6009335074727417445L;
    public static final String DEFAULT_CURRENCY_CODE = "CNY";
    public static final int DEFAULT_ROUNDING_MODE = 6;
    public static final int DEFAULT_EXPAND_LENGTH = 6;
    public static final int DEFAULT_DIVIDE_LENGTH = 6;
    public static final int DEFAULT_EXPAND_VALUE = 1000000;
    private static final int[] centFactors = new int[]{1, 10, 100, 1000};
    private static final Long max_amount_value = 9223372036854L;
    private static final int max_amount_length = 13;
    private static final int max_long_length = String.valueOf(Long.MAX_VALUE).length();
    private long calculate_amount;
    private BigDecimal calculate_big_amount;
    private long cent;
    private Currency currency;

    public Money() {
        this(0.0);
    }

    public Money(long yuan, int cent) {
        this(yuan, cent, Currency.getInstance("CNY"));
    }

    public Money(long yuan, int cent, Currency currency) {
        this.currency = currency;
        if ((yuan + "").length() >= 13) {
            this.calculate_big_amount = (new BigDecimal(yuan)).add(BigDecimal.valueOf((long) cent, currency.getDefaultFractionDigits()));
        }

        this.cent = yuan * (long) this.getCentFactor() + (long) cent;
        this.calculate_amount = this.cent * 1000000L / (long) this.getCentFactor();
    }

    public Money(String amount) {
        this(amount, Currency.getInstance("CNY"));
    }

    public Money(String amount, Currency currency) {
        this(new BigDecimal(amount), currency);
    }

    public Money(String amount, Currency currency, int roundingMode) {
        this(new BigDecimal(amount), currency, roundingMode);
    }

    public Money(double amount) {
        this(amount, Currency.getInstance("CNY"));
    }

    public Money(double amount, Currency currency) {
        this.currency = currency;
        if (amount > (double) max_amount_value) {
            this.calculate_big_amount = new BigDecimal(amount);
        } else {
            this.calculate_amount = Math.round(amount * 1000000.0);
        }

    }

    public Money(BigDecimal amount) {
        this(amount, Currency.getInstance("CNY"));
    }

    public Money(BigDecimal amount, int roundingMode) {
        this(amount, Currency.getInstance("CNY"), roundingMode);
    }

    public Money(BigDecimal amount, Currency currency) {
        this((BigDecimal) amount, currency, 6);
    }

    public Money(BigDecimal amount, Currency currency, int roundingMode) {
        this.currency = currency;
        this.assertOutOfBound(amount.toString());
        if (this.calculate_big_amount == null) {
            this.calculate_amount = this.rounding(amount.movePointRight(6), roundingMode);
        }

    }

    public BigDecimal getAmount() {
        return this.calculate_big_amount != null ? this.calculate_big_amount : BigDecimal.valueOf(this.calculate_amount, 6);
    }

    public void setAmount(BigDecimal amount) {
        if (amount != null) {
            this.assertOutOfBound(amount.toString());
            if (this.calculate_big_amount == null) {
                this.calculate_amount = this.rounding(amount.movePointRight(6), 6);
            }
        }

    }

    public long getCent() throws RuntimeException {
        if (this.calculate_big_amount != null) {
            throw new RuntimeException("超出long金额范围,请调用方法getAmount!");
        } else {
            return this.calculate_amount * (long) this.getCentFactor() / 1000000L;
        }
    }

    public Currency getCurrency() {
        return this.currency;
    }

    public int getCentFactor() {
        return centFactors[this.currency.getDefaultFractionDigits()];
    }

    public boolean equals(Object other) {
        if (other == null) {
            return false;
        } else if (!(other instanceof Money)) {
            return false;
        } else {
            Money m = (Money) other;
            if (m.calculate_big_amount != null) {
                return this.currency.equals(m.currency) && this.getAmount().compareTo(m.calculate_big_amount) == 0;
            } else {
                return this.currency.equals(m.currency) && this.calculate_amount == m.calculate_amount;
            }
        }
    }

    public int hashCode() {
        return this.calculate_big_amount != null ? this.calculate_big_amount.hashCode() ^ this.calculate_big_amount.hashCode() >>> 32 : (int) (this.calculate_amount ^ this.calculate_amount >>> 32);
    }

    public int compareTo(Object other) {
        return this.compareTo((Money) other);
    }

    public int compareTo(Money other) {
        this.assertSameCurrencyAs(other);
        if (other.calculate_big_amount == null && this.calculate_big_amount == null) {
            if (this.calculate_amount < other.calculate_amount) {
                return -1;
            } else {
                return this.calculate_amount == other.calculate_amount ? 0 : 1;
            }
        } else {
            return this.getAmount().compareTo(other.calculate_big_amount);
        }
    }

    public boolean greaterThan(Money other) {
        return this.compareTo(other) > 0;
    }

    public Money add(Money other) {
        return other.calculate_big_amount == null && this.calculate_big_amount == null && Math.max((this.calculate_amount + "").length(), (other.calculate_amount + "").length()) + 1 < max_long_length ? this.newMoneyWithSameCurrency(this.calculate_amount + other.calculate_amount) : this.newMoneyWithSameCurrency(this.getAmount().add(other.getAmount()));
    }

    public Money addTo(Money other) {
        this.assertSameCurrencyAs(other);
        if (other.calculate_big_amount == null && this.calculate_big_amount == null && Math.max((this.calculate_amount + "").length(), (other.calculate_amount + "").length()) + 1 < max_long_length) {
            this.calculate_amount += other.calculate_amount;
            return this;
        } else {
            this.calculate_big_amount = this.getAmount().add(other.getAmount());
            return this;
        }
    }

    public Money subtract(Money other) {
        this.assertSameCurrencyAs(other);
        return other.calculate_big_amount == null && this.calculate_big_amount == null ? this.newMoneyWithSameCurrency(this.calculate_amount - other.calculate_amount) : this.newMoneyWithSameCurrency(this.getAmount().subtract(other.getAmount()));
    }

    public Money subtractFrom(Money other) {
        this.assertSameCurrencyAs(other);
        if (other.calculate_big_amount == null && this.calculate_big_amount == null) {
            this.calculate_amount -= other.calculate_amount;
            return this;
        } else {
            this.calculate_big_amount = this.getAmount().subtract(other.getAmount());
            return this;
        }
    }

    public Money multiply(long val) {
        return this.calculate_big_amount == null && this.getAddLength(this.calculate_amount, val) < max_long_length ? this.newMoneyWithSameCurrency(this.calculate_amount * val) : this.newMoneyWithSameCurrency(this.getAmount().multiply(new BigDecimal(val)));
    }

    public Money multiplyBy(long val) {
        if (this.calculate_big_amount == null && this.getAddLength(this.calculate_amount, val) < max_long_length) {
            this.calculate_amount *= val;
            return this;
        } else {
            this.calculate_big_amount = this.getAmount().multiply(new BigDecimal(val));
            return this;
        }
    }

    public Money multiply(double val) {
        return this.calculate_big_amount == null && this.getAddLength(this.calculate_amount, val) < max_long_length ? this.newMoneyWithSameCurrency(Math.round((double) this.calculate_amount * val)) : this.newMoneyWithSameCurrency(this.getAmount().multiply(new BigDecimal(val)));
    }

    public Money multiplyBy(double val) {
        if (this.calculate_big_amount == null && this.getAddLength(this.calculate_amount, val) < max_long_length) {
            this.calculate_amount = Math.round((double) this.calculate_amount * val);
            return this;
        } else {
            this.calculate_big_amount = this.getAmount().multiply(new BigDecimal(val));
            return this;
        }
    }

    public Money multiply(BigDecimal val) {
        return this.multiply(val, 6);
    }

    public Money multiplyBy(BigDecimal val) {
        return this.multiplyBy(val, 6);
    }

    public Money multiply(BigDecimal val, int roundingMode) {
        BigDecimal newcalculate_amount = this.getAmount().multiply(val);
        return this.isOutOfBound(newcalculate_amount.toString()) ? this.newMoneyWithSameCurrency(newcalculate_amount) : this.newMoneyWithSameCurrency(this.rounding(newcalculate_amount.movePointRight(6), roundingMode));
    }

    public Money multiplyBy(BigDecimal val, int roundingMode) {
        BigDecimal newcalculate_amount = this.getAmount().multiply(val);
        this.assertOutOfBound(newcalculate_amount.toString());
        if (this.calculate_big_amount == null) {
            this.calculate_amount = this.rounding(newcalculate_amount.movePointRight(6), roundingMode);
        }

        return this;
    }

    public Money divide(double val) {
        return this.calculate_big_amount == null && !((double) this.calculate_amount / val >= 9.223372036854776E18) ? this.newMoneyWithSameCurrency(Math.round((double) this.calculate_amount / val)) : this.newMoneyWithSameCurrency(this.getAmount().divide(new BigDecimal(val), 6, 6));
    }

    public Money divideBy(double val) {
        if (this.calculate_big_amount == null && !((double) this.calculate_amount / val > (double) max_amount_value)) {
            this.calculate_amount = Math.round((double) this.calculate_amount / val);
            return this;
        } else {
            this.calculate_big_amount = this.getAmount().divide(new BigDecimal(val), 6, 6);
            return this;
        }
    }

    public Money divide(BigDecimal val) {
        return this.divide(val, 6);
    }

    public Money divide(BigDecimal val, int roundingMode) {
        BigDecimal newcalculate_amount = this.getAmount().divide(val, 6, roundingMode);
        return this.isOutOfBound(newcalculate_amount.toString()) ? this.newMoneyWithSameCurrency(newcalculate_amount) : this.newMoneyWithSameCurrency(this.rounding(newcalculate_amount.movePointRight(6), roundingMode));
    }

    public Money divideBy(BigDecimal val) {
        return this.divideBy(val, 6);
    }

    public Money divideBy(BigDecimal val, int roundingMode) {
        BigDecimal newcalculate_amount = this.getAmount().divide(val, 6, roundingMode);
        if (this.isOutOfBound(newcalculate_amount.toString())) {
            this.calculate_big_amount = newcalculate_amount;
            return this;
        } else {
            this.calculate_amount = this.rounding(newcalculate_amount.movePointRight(6), roundingMode);
            return this;
        }
    }

    public Money[] allocate(int targets) {
        Money[] results = new Money[targets];
        Money lowResult;
        if (this.calculate_big_amount != null) {
            lowResult = this.newMoneyWithSameCurrency(this.calculate_big_amount.divide(new BigDecimal(targets), 6, 6));

            for (int i = 0; i < targets; ++i) {
                results[i] = lowResult;
            }

            return results;
        } else {
            lowResult = this.newMoneyWithSameCurrency(this.calculate_amount / (long) targets);
            Money highResult = this.newMoneyWithSameCurrency(lowResult.calculate_amount + 1L);
            int remainder = (int) this.calculate_amount % targets;

            int i;
            for (i = 0; i < remainder; ++i) {
                results[i] = highResult;
            }

            for (i = remainder; i < targets; ++i) {
                results[i] = lowResult;
            }

            return results;
        }
    }

    public Money[] allocate(long[] ratios) {
        Money[] results = new Money[ratios.length];
        long total = 0L;

        for (int i = 0; i < ratios.length; ++i) {
            total += ratios[i];
        }

        long remainder = this.calculate_amount;

        int i;
        for (i = 0; i < results.length; ++i) {
            results[i] = this.newMoneyWithSameCurrency(this.calculate_amount * ratios[i] / total);
            remainder -= results[i].calculate_amount;
        }

        for (i = 0; (long) i < remainder; ++i) {
            ++results[i].calculate_amount;
        }

        return results;
    }

    public String toString() {
        return this.getAmount().toString();
    }

    protected void assertSameCurrencyAs(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Money math currency mismatch.");
        }
    }

    protected long rounding(BigDecimal val, int roundingMode) {
        return val.setScale(0, roundingMode).longValue();
    }

    private void assertOutOfBound(String value) {
        if (this.isOutOfBound(value)) {
            this.calculate_big_amount = new BigDecimal(value);
        }

    }

    private Boolean isOutOfBound(String value) {
//        int length = false;
        int length;
        if (value.contains(".")) {
            length = value.split("\\.")[0].length();
        } else {
            length = value.length();
        }

        return length >= 13 ? true : false;
    }

    private int getAddLength(Object x1, Object x2) {
//        int length = false;
        String value = x2.toString();
        int length;
        if (value.contains(".")) {
            length = value.split("\\.")[0].length();
        } else {
            length = value.length();
        }

        return x1.toString().length() + length;
    }

    protected Money newMoneyWithSameCurrency(long calculate_amount) {
        Money money = new Money(0.0, this.currency);
        money.calculate_amount = calculate_amount;
        return money;
    }

    protected Money newMoneyWithSameCurrency(BigDecimal calculate_big_amount) {
        Money money = new Money(0.0, this.currency);
        money.calculate_big_amount = calculate_big_amount;
        return money;
    }

    public String dump() {
        String lineSeparator = System.getProperty("line.separator");
        StringBuffer sb = new StringBuffer();
        sb.append("amount = ").append(this.getAmount()).append(lineSeparator);
        sb.append("currency = ").append(this.currency);
        return sb.toString();
    }

    public String getCurrencyCode() {
        return this.currency.getCurrencyCode();
    }

    public void setCurrencyCode(String currencyCode) {
        this.currency = Currency.getInstance(currencyCode);
    }

    public void setYuan(BigDecimal yuan) {
        if (yuan != null) {
            this.assertOutOfBound(yuan.movePointLeft(6).toString());
            if (this.calculate_big_amount == null) {
                this.calculate_amount = this.rounding(yuan, 6);
            }
        }

    }

    public BigDecimal getYuan() {
        return this.calculate_big_amount != null ? this.getAmount().movePointRight(6) : new BigDecimal(this.calculate_amount);
    }

    public long getLongYuan() {
        if (this.calculate_big_amount != null) {
            throw new RuntimeException("超出Long的最大值");
        } else {
            return this.calculate_amount;
        }
    }

    public BigDecimal getDecimalYuan() {
        return this.calculate_big_amount != null ? this.calculate_big_amount.movePointRight(6).setScale(0, 6) : new BigDecimal(this.calculate_amount);
    }

    public BigDecimal getNegeteYuan() {
        return this.calculate_big_amount != null ? this.getAmount().movePointRight(6).negate() : (new BigDecimal(this.calculate_amount)).negate();
    }

    public String getAmount2Figures() {
        String amount2Figures = this.getAmount().toString();
        return !amount2Figures.contains(".") ? amount2Figures + ".00" : amount2Figures.substring(0, amount2Figures.length() - 4);
    }

    public static boolean isIntegerValue(BigDecimal bd) {
        return bd.signum() == 0 || bd.scale() <= 0 || bd.stripTrailingZeros().scale() <= 0;
    }

    public boolean isIntegerValue() {
        return this.calculate_big_amount.signum() == 0 || this.calculate_big_amount.scale() <= 0 || this.calculate_big_amount.stripTrailingZeros().scale() <= 0;
    }
}